// encoding utf-8
#ifndef __QpwNetwork__
#define __QpwNetwork__

#include <QtCore/QUuid>
#include <QtCore/QVariant>
#include <QtNetwork/QTcpSocket>
#include <QyNet.h>

//! Система передачи данных между клиенской и серверной частью PiperWar
namespace QpwNetwork {

class Server;
class ContentSynchronizer;

/*! \brief Типы дейтаграмм
*/
enum DatagramTypes {
    VariantDatagramType = 0x10, //!< Произвольные пользовательские данные
    FilePartDatagramType, //!< Данные передаваемого файла
    UserDatagramType = 0x200 //!< Начало пространства для пользовательских дейтаграмм
};

/*! \brief Дейтаграмма содержащая произвольные данные

VariantDatagram позволяет передавать абстрактный набор данных, а так же задавать некий тип данных.


*/
class VariantDatagram : public QyNet::AbstractDatagramEncoder {
public:
    enum DataTypes {
        SystemVariantDataType,
        CustomVariantDataType = 0x100
    };
    VariantDatagram() {}
    explicit VariantDatagram(
        int dataType,
        const QVariant & data1 = QVariant(),
        const QVariant & data2 = QVariant(),
        const QVariant & data3 = QVariant(),
        const QVariant & data4 = QVariant(),
        const QVariant & data5 = QVariant()
    ); //!< конструктор определен для пяти переменных, можно передавать больше
    virtual ~VariantDatagram() {}

    virtual quint32 type() const { return VariantDatagramType; }
    virtual QByteArray encode() const;

    virtual void decode( QByteArray data );

    quint32 dataType; //!< Тип данных, устанавливается на передающей стороне пользоателем, принимающая сторона, по этому типу может распознать, какого рода данные ей прислали
    QList<QVariant> data; //!< непосредственно сами данные, кол-во и размер не ограничено, но следите за тем чтобы размер был разумным, иначе могут возникнуть проблемы

};

/*! \brief Дейтаграмма для передачи файлов по частям

Чтобы отправить файл, достаточно вызвать статический метод sendFile.

На принимающей стороне, при приеме дейтаграммы, следует вызвать метод write. Целостность файла при этом не проверяеться.

*/
class FilePartDatagram : public QyNet::AbstractDatagramEncoder {
public:
    FilePartDatagram() {}
    virtual ~FilePartDatagram() {}

    virtual quint32 type() const { return FilePartDatagramType; }
    QString originalFileName() const { return fileName; } //!< Оригинальное имя файла
    QString originalFilePath() const { return filePath; } //!< Оригиналный путь к файлу

    QByteArray encode() const;
    void decode( QByteArray dataArray );
    bool write( const QString & filePath ); //!< Записать принятую часть данных в файл
    qint64 dataSize() const { return fileData.size(); } //!< Размер принятых данных файла
    qint64 totalFileSize() const { return totalSize; } //!< Размер файла
    qint64 filePos() const { return pos; }  //!< Адресс данных в файле
    qint64 totalFileParts() const { return totalParts; } //!< На сколько всего частей разбит файл
    qint64 filePart() const { return part; }  //!< Номер текущей части файла
    static bool sendFile( const QString & filePath, QyNet::ConnectionRecord * clientRecord,
        quint32 blockSize = 256 * 1024 ); //!< Отправит файл

private:
    qint64 totalSize,
           pos,
           totalParts;
    quint32 part;
    QString fileName;
    QString filePath;
    QByteArray fileData;

};

/*! \brief Запись о соединении с клиентом на сервере
*/
class ClientRecord : public QyNet::ConnectionRecord {
    Q_OBJECT
public:
    explicit ClientRecord( QTcpSocket * socket, Server * server );
    virtual ~ClientRecord() { socket()->deleteLater(); }

    QString login() const { return p_login; } //!< Имя пользователя
    QUuid uuid() const { return p_uuid; }  //!< Уникальный идентификатор, выдаеться при авторизации
    bool isAuthorised() const
        { return !p_uuid.isNull() && socket()->state() != QTcpSocket::UnconnectedState; } //!< Авторизован ли пользователь
    quint32 level() const { return p_level; } //!< Уровень доступа пользователя
    void logout();

signals:
    void authorised(); //!< Соединение авторизовано

private slots:
    void onDatagramReceived( QyNet::AbstractDatagramEncoder * datagram );
    void onDisconnected();

private:
    void timerEvent( QTimerEvent * event );

    QString p_login;
    QUuid p_uuid;
    Server * p_server;
    int p_killTimerId;
    quint32 p_level;

};

/*! \brief Менеджер учетных записей пользователей
*/
class AccountManager {
public:
    virtual ~AccountManager() {}

    virtual bool checkUser( const QString & login, const QString & password )
        { Q_UNUSED(login); Q_UNUSED(password); return true; } //!< Проверить, может ли пользователь с таким именем и паролем соединиться
    virtual bool loginUser( const QString & login, const QString & password )
        { Q_UNUSED(login); Q_UNUSED(password); return true; } //!< Проверить и авторизовать пользователя
    virtual void logoutUser( const QString & login ) { Q_UNUSED(login); } //!< Завершить сеанс пользователя
    virtual bool checkIp( const QHostAddress & address )
        { Q_UNUSED(address); return true; }  //!<  проверить адресс, возможена ли с него авторизачия
    virtual quint32 level( const QString & login ) const { return 0; } //!< получить уровень доступа пользователя

};

/*! \brief Сервер занимается авторизацией пользователей(клиентов) и синхронизацией данных

*/
class Server : public QyNet::Server {
    Q_OBJECT
public:
	explicit Server( QObject * parent = 0 );
    explicit Server( quint16 port = 0, const QHostAddress & address = QHostAddress::Any,
        QObject * parent = 0 );
    virtual ~Server();

    AccountManager * accountManager() const { return p_accountManager; } //!< Менеджер учетных записей
    QList<ClientRecord*> authorised() const { return p_authorised; } //!< Список авторизованных пользователей
    QpwNetwork::ContentSynchronizer * synchronizer() const { return p_synchronizer; } //!< синхронизатор данных

    void setAccountManager( AccountManager * accountManager ) { p_accountManager = accountManager; } //!< Установить менеджер учетных записей

public slots:
    void sendAllAuthorised( QyNet::AbstractDatagramEncoder * datagram );

signals:
    void clientAuthorised( QpwNetwork::ClientRecord * clientRecord ); //!< Клиент авторизован
    void clientDisconnected( QpwNetwork::ClientRecord * clientRecord ); //!< Связь с клиентом потеряна

private slots:
    void clientAuthorised();
    void clientDisconnected();

protected:
    virtual QyNet::ConnectionRecord * createConnectionRecord( QTcpSocket * socket );

private:
    AccountManager * p_accountManager;
    QList<ClientRecord*> p_authorised;
    ContentSynchronizer * p_synchronizer;

};

/*! \brief Слиент позволяет авторизоваться на сервере и получить необходимые данные
*/
class Client : public QyNet::Client {
    Q_OBJECT
public:
    explicit Client( QObject * parent = 0 ) : QyNet::Client(parent) { init(); }
    explicit Client( const QHostAddress & address = QHostAddress("127.0.0.1"), quint16 port = 0,
        QObject * parent = 0 );

    QUuid uuid() const { return p_uuid; } //!< Уникальный номер сеанса
    bool isAuthorised() const
        { return !p_uuid.isNull() && socket()->state() != QTcpSocket::UnconnectedState; } //!< Авторизован ли клиент
    QpwNetwork::ContentSynchronizer * synchronizer() const { return p_synchronizer; } //!< синхронизатор

public slots:
    void login( const QHostAddress & address, quint16 port, const QString & username,
        const QString & password ); //!< Войти на сервер

signals:
    void authorised(); //!< Авторизация прошла успешно

private slots:
    void onDatagramReceived( QyNet::AbstractDatagramEncoder * datagram );
    void onDisconnected();

private:
    void init();

    QUuid p_uuid;
    QString p_login,
        p_password;
    ContentSynchronizer * p_synchronizer;

};

/*! \brief
*/
class ContentSynchronizer : public QObject {
    Q_OBJECT
public:
    explicit ContentSynchronizer( const QString & basePath = QString(), QObject * parent = 0 );
    virtual ~ContentSynchronizer();

    QString basePath() const { return p_basePath; }
    bool isAcceptFiles() const { return p_acceptFiles; }
    bool checkFile( const QString & filePath, const QByteArray & hash );

public slots:
    void setBasePath( const QString & basePath ) { p_basePath = basePath; }
    void addFile( const QString & filePath );
    void removeFile( const QString & filePath );
    void syncFrom( QyNet::ConnectionRecord * connectionRecord );
    void syncTo( QyNet::ConnectionRecord * connectionRecord );
    void setAcceptFiles( bool acceptFiles ) { p_acceptFiles = true; }
    void addAllFrom( QString path = QString(), bool recursive = true );

signals:
    void beginSynchronisation();
    void synchronisationProgress( const QString & filePath, qint64 current, qint64 total );
    void synchronisationComplete();

private slots:
    void processIncommingSyncData( QyNet::AbstractDatagramEncoder * datagram );

private:
    class FileInfo {
    public:
        explicit FileInfo( const QString & filePath, const QString & basePath = QString(),
            bool calcHash = true );

        QString filePath() const { return p_filePath; }
        QByteArray hash() const { return p_hash; }
        qint64 size() const { return p_size; }

        inline bool operator == ( const FileInfo & fileInfo ) { return filePath() == fileInfo.filePath(); }
        inline bool operator != ( const FileInfo & fileInfo ) { return filePath() != fileInfo.filePath(); }

    private:
        QString p_filePath;
        QByteArray p_hash;
        qint64 p_size;

    };
    class FileLoadingInfo {
    public:
        FileLoadingInfo( qint64 size = 0 ) : current(0), p_size(size) {}

        qint64 size() const { return p_size; }
        qint64 current;

    private:
        qint64 p_size;

    };

    void sendAllFiles( QyNet::ConnectionRecord * connectionRecord );

    QString p_basePath;
    QList<FileInfo> p_files;
    bool p_acceptFiles;
    QMap<QString,FileLoadingInfo> p_fileLoadingInfos;

};

};

#endif//__QpwNetwork__
